package com.isyscore.os.metadata.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.isyscore.os.core.exception.DataFactoryException;
import com.isyscore.os.core.exception.ErrorCode;
import com.isyscore.os.metadata.common.CommonService;
import com.isyscore.os.metadata.dao.MetricMapper;
import com.isyscore.os.metadata.database.AbstractDatabase;
import com.isyscore.os.metadata.enums.AggregationType;
import com.isyscore.os.metadata.enums.TimePeriodUnit;
import com.isyscore.os.metadata.manager.DatabaseManager;
import com.isyscore.os.metadata.model.dto.*;
import com.isyscore.os.metadata.model.dto.req.MetricCreateUpdateReq;
import com.isyscore.os.metadata.model.entity.*;
import com.isyscore.os.metadata.model.vo.DataQueryVO;
import com.isyscore.os.metadata.utils.TimePeriodUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

import static com.isyscore.os.core.exception.ErrorCode.*;
import static com.isyscore.os.metadata.constant.CommonConstant.*;
import static java.util.Collections.emptyList;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author
 * @since 2021-08-11
 */
@Service
public class MetricService extends CommonService<MetricMapper, Metric> {

    @Autowired
    private MetricCalculateService metricCalculateService;

    @Autowired
    private FormulaService formulaService;

    @Autowired
    private DatamodelService dataModelService;

    @Autowired
    private SqlStatementService sqlStatementService;

    @Autowired
    private MetricDimFilterService metricDimFilterService;

    @Autowired
    private MetricGroupService metricGroupService;

    @Autowired
    private MetricJobService metricJobService;

    @Autowired
    private MetricJobBatchResultService metricJobBatchResultService;

    @Autowired
    private MetricJobBatchService metricJobBatchService;

    @Autowired
    private MetricMeasureService metricMeasureService;

    @Autowired
    private DataSourceServiceImpl dataSourceService;

    @Autowired
    private DatamodelColumnService datamodelColumnService;

    private final MetricResultComparator metricResultComparator = new MetricResultComparator();

    @Value("${openapi.metric-job-result-api}")
    private String metricJobResultApiPath;

    /**
     * 创建或派生一个指标
     */
    @Transactional(rollbackFor = Exception.class)
    public Metric createMetric(MetricCreateUpdateReq req) {
        Metric metric = new Metric();
        convertReq2Metric(req, metric);
        Metric parentMetric = null;
        if (req.getParentMetricId() != null) {
            parentMetric = this.getMetricById(req.getParentMetricId());
        }
        if (parentMetric == null) {
            metric.setExtendPath(METRIC_PATH_SPLITTER);
        }
        this.save(metric);
        //更新继承路径
        if (parentMetric != null) {
            String parentPath = "";
            if (parentMetric.getExtendPath().equals(METRIC_PATH_SPLITTER)) {
                parentPath = METRIC_PATH_SPLITTER + parentMetric.getId();
            } else {
                parentPath = parentPath + parentMetric.getExtendPath();
            }
            String path = parentPath + METRIC_PATH_SPLITTER + metric.getId();
            metric.setExtendPath(path);
        }
        //保存维度条件
        metricDimFilterService.saveMetricFilter(metric.getId(), req.getFilters());
        //保存度量及聚合方式
        metricMeasureService.saveMetricMeasure(metric.getId(), req.getMeasures());
        //刷新定时任务信息
        if (!Strings.isNullOrEmpty(metric.getTaskCron())) {
            metricJobService.refreshMetricJob(metric.getId(), metric.getTaskCron());
        }
        //解析指标SQL
        String sql = this.parseMetricSql(metric);
        Long sqlId = sqlStatementService.saveSql(sql);
        metric.setSqlRefId(sqlId);
        this.updateById(metric);
        return metric;
    }

    /**
     * 修改指标
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateMetric(Long metricId, MetricCreateUpdateReq req) {
        Metric metric = this.getMetricById(metricId);
        convertReq2Metric(req, metric);
        this.updateById(metric);
        //保存维度条件
        metricDimFilterService.saveMetricFilter(metric.getId(), req.getFilters());
        //保存度量及聚合方式
        metricMeasureService.saveMetricMeasure(metric.getId(), req.getMeasures());
        //处理派生出的指标的SQL更新, 衍生指标更新需要放在父系指标更新之后, 解析sql需要拿取父系指标的度量聚合方式
        this.updateExtendMetricSql(metric);
        //刷新定时任务信息
        if (!Strings.isNullOrEmpty(metric.getTaskCron())) {
            metricJobService.refreshMetricJob(metric.getId(), metric.getTaskCron());
        } else {
            metricJobService.deleteMetricJob(metric.getId());
        }
        //替换指标SQL
        String newSql = this.parseMetricSql(metric);
        sqlStatementService.updateSql(metric.getSqlRefId(), newSql);
    }

    /**
     * 删除指标
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteMetric(Long id) {
        Metric metric = this.getMetricById(id);
        List<Metric> extendMetrics = this.getAllExtendMetricByParent(metric.getId());
        if (extendMetrics.size() > 0) {
            throw new DataFactoryException(DATA_NOT_ALLOWED_DELETE, "该指标存在已派生的指标，无法删除");
        }
        sqlStatementService.removeById(metric.getSqlRefId());
        metricDimFilterService.deleteFilterByMetricId(metric.getId());
        metricJobService.deleteMetricJob(metric.getId());
        metricJobBatchResultService.removeResultsByMetric(metric.getId());
        metricMeasureService.deleteMetricMeasure(id);
        this.removeById(metric.getId());
    }

    public MetricDTO getMetricDetailById(Long metricId) {
        MetricDTO detail = new MetricDTO();
        Metric metric = this.getFullMetricInfoById(metricId);
        detail.setMetricJobResultApiPath(metricJobResultApiPath + "/" + metric.getId());
        detail.setId(metric.getId());
        detail.setGroupId(metric.getGroupRefId());
        detail.setName(metric.getName());
        detail.setDescription(metric.getDescription());
        detail.setDatamodelId(metric.getDatamodelRefId());
        detail.setTaskCron(metric.getTaskCron());
        String sql = sqlStatementService.getSqlById(metric.getSqlRefId());
        sql = sql.replace(METRIC_SQL_DYNAMIC_FILTERS, "");
        detail.setSql(sql);
        Datamodel datamodel = dataModelService.getDatamodelById(metric.getDatamodelRefId());
        detail.setDatamodelName(datamodel.getName());
        detail.setDimension(metric.getDimension());
        List<MetricMeasureDTO> measures = metric.getMeasures().stream().map((entity -> {
            MetricMeasureDTO dto = new MetricMeasureDTO();
            dto.copyFromEnity(entity);
            return dto;
        })).collect(Collectors.toList());
        detail.setMeasures(measures);
        detail.setFormula(metric.getFormula());
        detail.setTimePeriodDim(metric.getTimePeriodDim());
        detail.setTimePeriodUnit(metric.getTimePeriodUnit());
        detail.setTimePeriodNumber(metric.getTimePeriodNumber());
        detail.setFilters(metricDimFilterService.getFilterByMetricId(detail.getId()));
        if (!metric.getExtendPath().equals(METRIC_PATH_SPLITTER)) {
            detail.setExtended(true);
        }
        return detail;
    }

    public List<Metric> getAllExtendMetricByParent(Long parentId) {
        LambdaQueryWrapper<Metric> qw = new LambdaQueryWrapper<>();
        qw.like(Metric::getExtendPath, parentId);
        qw.ne(Metric::getId, parentId);
        return this.list(qw);
    }

    private void convertReq2Metric(MetricCreateUpdateReq req, Metric metric) {
        metric.setName(req.getName());
        metric.setDescription(req.getDescription());
        if (!Strings.isNullOrEmpty(req.getTimePeriodDim())) {
            metric.setTimePeriodDim(req.getTimePeriodDim());
            metric.setTimePeriodUnit(req.getTimePeriodUnit());
            metric.setTimePeriodNumber(req.getTimePeriodNumber());
        } else {
            metric.setTimePeriodDim("");
            metric.setTimePeriodUnit("");
            metric.setTimePeriodNumber(1);
        }
        if (req.getGroupId() == null) {
            metric.setGroupRefId(DEFAULT_METRIC_GROUP_ID);
        } else {
            metric.setGroupRefId(req.getGroupId());
        }
        if (req.getParentMetricId() == null) {
            //非派生指标
            metric.setDatamodelRefId(req.getDatamodelId());
            //计算公式处理
            if (!Strings.isNullOrEmpty(req.getFormula())) {
                List<String> measureCols = req.getMeasures().stream().map(MetricMeasureDTO::getMeasureColName).collect(Collectors.toList());
                formulaService.testFormula(req.getFormula(), measureCols);
                metric.setFormula(req.getFormula());
            } else {
                metric.setFormula("");
            }
        }
        if (req.getDimension() != null) {
            metric.setDimension(req.getDimension());
        } else {
            metric.setDimension("");
        }
    }

    public String parseMetricSql(Metric metric) {
        Metric root = this.getRootMetricOfExtendMetric(metric);
        List<MetricMeasure> measures = metricMeasureService.getMeasuresByMetricId(root.getId());
        List<MetricDimFilterDTO> filters = metricDimFilterService.getFilterByMetricId(metric.getId());
        StringBuilder sql = new StringBuilder();
        Datamodel datamodel = dataModelService.getDatamodelById(root.getDatamodelRefId());
        String baseSql = sqlStatementService.getSqlById(datamodel.getSqlRefId());
        DataSourceDTO dataSource = dataSourceService.getDataSource(datamodel.getDsRefId());
        AbstractDatabase db = DatabaseManager.findDb(dataSource);
        sql.append("SELECT ");
        for (MetricMeasure measure : measures) {
            if (!Strings.isNullOrEmpty(measure.getMeasureAggType())) {
                sql.append(AggregationType.valueOf(measure.getMeasureAggType()).getAggregationEl(db.escapeColName(measure.getMeasureColName())));
                sql.append(" AS ");
            }
            sql.append(db.escapeColAliasName(measure.getMeasureColName()));
            sql.append(",");
        }
        //如果存在统计维度，也需要查询出统计维度的值
        if (!Strings.isNullOrEmpty(metric.getDimension())) {
            sql.append(db.escapeColName(metric.getDimension()));
        } else {
            sql.deleteCharAt(sql.lastIndexOf(","));
        }
        sql.append(" FROM (");
        sql.append(baseSql);
        sql.append(") base");
        boolean firstWhereClause = true;
        if (!Strings.isNullOrEmpty(metric.getTimePeriodDim())) {
            sql.append(" WHERE ");
            firstWhereClause = false;
            DatamodelColumn column = datamodelColumnService.getColumnByName(datamodel.getId(), metric.getTimePeriodDim());
            if (!db.isLegalDateType(column.getColDataType())) {
                throw new DataFactoryException(INVALID_DATETIME_TYPE, column.getColName());
            }
            sql.append(db.escapeColName(metric.getTimePeriodDim()));
            sql.append(" between " + METRIC_TIME_PERIOD_START_TIME_PARAMNAME + " and " + METRIC_TIME_PERIOD_END_TIME_PARAMNAME);
        }
        for (MetricDimFilterDTO filter : filters) {
            if (firstWhereClause) {
                sql.append(" WHERE ");
                firstWhereClause = false;
            } else {
                sql.append(" AND ");
            }
            sql.append(metricDimFilterService.parseFilter2Sql(db, filter));
        }
        sql.append(METRIC_SQL_DYNAMIC_FILTERS);
        long aggMeasureCount = measures.stream().filter((m) -> !Strings.isNullOrEmpty(m.getMeasureAggType())).count();
        //同时存在维度和聚合计算方式的时候才需要添加groupby语句
        if (!Strings.isNullOrEmpty(metric.getDimension()) && aggMeasureCount > 0) {
            sql.append(" GROUP BY ");
            sql.append(db.escapeColName(metric.getDimension()));
        }
        return sql.toString();
    }

    public Metric getRootMetricOfExtendMetric(Metric extendMetric) {
        if (!extendMetric.isExtended()) {
            return extendMetric;
        }
        String[] ids = extendMetric.getExtendPath().split(METRIC_PATH_SPLITTER);
        String rootMetricId = ids[1];
        if (!Strings.isNullOrEmpty(rootMetricId)) {
            return getMetricById(Long.parseLong(rootMetricId));
        }
        return null;
    }

    public Metric getMetricById(Long id) {
        Metric metric = this.getById(id);
        if (metric == null) {
            throw new DataFactoryException(DATA_NOT_FOUND, "该数据指标不存在，请刷新页面重试");
        }
        return metric;
    }

    /**
     * 获取某个数据模型包含的所有根指标
     */
    public List<Metric> getAllRootMetricByDatamodel(Long datamodelId) {
        LambdaQueryWrapper<Metric> qw = new LambdaQueryWrapper<>();
        qw.eq(Metric::getDatamodelRefId, datamodelId);
        qw.eq(Metric::getExtendPath, METRIC_PATH_SPLITTER);
        return this.list(qw);
    }

    /**
     * 获取某个数据模型包含的所有指标
     */
    public List<Metric> getMetricByDatamodel(Long datamodelId) {
        LambdaQueryWrapper<Metric> qw = new LambdaQueryWrapper<>();
        qw.eq(Metric::getDatamodelRefId, datamodelId);
        return this.list(qw);
    }

    public List<MetricTreeItemDTO> getMetricAndGroupWithTree(String metricName) {
        List<Metric> metrics = null;
        LambdaQueryWrapper<Metric> qw = new LambdaQueryWrapper<>();
        if (!Strings.isNullOrEmpty(metricName)) {
            qw.like(Metric::getName, metricName);
        }
        qw.orderByDesc(Metric::getId);
        metrics = this.list(qw);
        LambdaQueryWrapper<MetricGroup> groupQw = new LambdaQueryWrapper<>();
        groupQw.orderByDesc(MetricGroup::getId);
        List<MetricGroup> groups = metricGroupService.list(groupQw);
        List<MetricTreeItemDTO> treeItems = Lists.newArrayList();
        Map<Long, MetricTreeItemDTO> groupMap = Maps.newLinkedHashMap();
        for (MetricGroup group : groups) {
            MetricTreeItemDTO groupItem = new MetricTreeItemDTO();
            groupItem.setId(group.getId());
            groupItem.setType(1);
            groupItem.setLabel(group.getGroupName());
            groupMap.put(groupItem.getId(), groupItem);
        }
        for (Metric metric : metrics) {
            MetricTreeItemDTO metricItem = new MetricTreeItemDTO();
            metricItem.setId(metric.getId());
            metricItem.setType(2);
            metricItem.setLabel(metric.getName());
            MetricTreeItemDTO group = groupMap.get(metric.getGroupRefId());
            if (group != null) {
                if (group.getChildren() == null) {
                    group.setChildren(Lists.newArrayList());
                }
                group.getChildren().add(metricItem);
            } else {
                treeItems.add(metricItem);
            }
        }
        Collection<MetricTreeItemDTO> groupItems = groupMap.values();
        //仅将存在子节点的分组（如果查询条件为空则全部加入）加入到树形结构中
        for (MetricTreeItemDTO groupItem : groupItems) {
            if (groupItem.getChildren() != null || Strings.isNullOrEmpty(metricName)) {
                treeItems.add(groupItem);
            }
        }
        return treeItems;
    }

    public Integer countMetricByDatamodel(Long datamodelId) {
        LambdaQueryWrapper<Metric> qw = new LambdaQueryWrapper<>();
        qw.eq(Metric::getDatamodelRefId, datamodelId);
        return this.count(qw);
    }

    public Metric getFullMetricInfoById(Long metricId) {
        Metric metric = this.getMetricById(metricId);
        //如果是派生指标，将根节点的基本信息设置到当前派生指标上，方便使用
        Metric rootMetric = this.getRootMetricOfExtendMetric(metric);
        metric.setFormula(rootMetric.getFormula());
        metric.setDatamodelRefId(rootMetric.getDatamodelRefId());
        List<MetricMeasure> measures = metricMeasureService.getMeasuresByMetricId(rootMetric.getId());
        metric.setMeasures(measures);
        return metric;
    }

    /**
     * 获取某个根指标的所有派生指标
     */
    public List<Metric> getExtendMetricsByRoot(Long rootMetricId) {
        LambdaQueryWrapper<Metric> qw = new LambdaQueryWrapper<>();
        qw.like(Metric::getExtendPath, rootMetricId);
        return this.list(qw);
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateExtendMetricSql(Metric rootMetric) {
        if (rootMetric.isExtended()) {
            return;
        }
        List<Metric> extendMetrics = this.getAllExtendMetricByParent(rootMetric.getId());
        for (Metric extendMetric : extendMetrics) {
            String newSql = parseMetricSql(extendMetric);
            sqlStatementService.updateSql(extendMetric.getSqlRefId(), newSql);
        }
    }


    public List<TimePeriodDTO> getPeriodsByMetric(Long metricId, Integer periodNumber) {
        if (periodNumber == null) {
            periodNumber = 5;
        }
        Metric metric = this.getFullMetricInfoById(metricId);
        if (Strings.isNullOrEmpty(metric.getTimePeriodDim())) {
            TimePeriodDTO defaultPeriod = new TimePeriodDTO();
            defaultPeriod.setKey(DEFAULT_METRIC_DATE_PERIOD_KEY);
            return Lists.newArrayList(defaultPeriod);
        }
        return TimePeriodUtil.getLatestPeriods(new Date(), TimePeriodUnit.valueOf(metric.getTimePeriodUnit()), metric.getTimePeriodNumber(), periodNumber);
    }

    /**
     * 查询指标的计算结果，如果为空，则直接通过指标计算得出结果，结果不会保存到数据库中
     * 支持结果集排序
     */
    public List<MetricResultDTO> queryMetricJobResultByPeriod(Long metricId, String timePeriod, String dimOrder, String dimension) {
        if (Strings.isNullOrEmpty(dimOrder)) {
            dimOrder = SORT_ASC;
        }
        if (!SORT_ASC.equalsIgnoreCase(dimOrder) && !SORT_DESC.equalsIgnoreCase(dimOrder)) {
            throw new DataFactoryException(ErrorCode.INVALID_ORDER_TYPE);
        }
        TimePeriodDTO timePeriodDto = getTimePeriodObjByKey(metricId, timePeriod);

        List<MetricJobBatchResult> jobBatchResults = metricJobBatchResultService.getResultsByMetricAndPeriod(metricId, timePeriodDto.getKey(), dimension);
        //没有查询到结果时，进行即时计算
        if (jobBatchResults.isEmpty()) {
            Metric metric = this.getFullMetricInfoById(metricId);
            DataQueryVO sqlExecuteReq = metricCalculateService.parseMetricSqlExecuteReq(metric, MAX_METRIC_DATA_RESULT_LIMIT, timePeriodDto, null);
            try {
                jobBatchResults = metricCalculateService.executeMetricSqlSync(sqlExecuteReq, metric, timePeriodDto);
            } catch (Exception e) {
                log.error("执行指标计算发生错误", e);
            }
            if (!jobBatchResults.isEmpty() && !Strings.isNullOrEmpty(dimension)) {
                jobBatchResults = jobBatchResults.stream().filter(result -> result.getDim().equals(dimension)).collect(Collectors.toList());
            }
        }
        jobBatchResults.sort(metricResultComparator);
        if (dimOrder.equals(SORT_DESC)) {
            Collections.reverse(jobBatchResults);
        }
        List<MetricResultDTO> results = Lists.newArrayList();
        for (MetricJobBatchResult r : jobBatchResults) {
            MetricResultDTO result = new MetricResultDTO();
            result.setDataPeriod(r.getDataPeriod());
            result.setCreateTime(r.getCreateTime());
            result.setDimension(r.getDim());
            result.setMeasure(r.getMeasure());
            results.add(result);
        }
        return results;
    }

    public List<Metric> getAllMetricWithJobInfo() {
        LambdaQueryWrapper<Metric> qw = new LambdaQueryWrapper<>();
        qw.isNotNull(Metric::getTaskCron);
        return this.list(qw);
    }

    @Transactional(rollbackFor = Exception.class)
    public void importData(List<Metric> datas) {
        if (datas.isEmpty()) {
            return;
        }
        Set<Long> metricIds = Sets.newConcurrentHashSet();
        for (Metric metric : datas) {
            metricIds.add(metric.getId());
        }
        //删除指标
        LambdaQueryWrapper<Metric> metricQw = new LambdaQueryWrapper<>();
        metricQw.in(Metric::getId, metricIds);
        this.remove(metricQw);
        this.saveBatch(datas);
    }

    public List<Metric> getMetricByGroupIds(Set<String> groupIds) {
        if (groupIds.isEmpty()) {
            return emptyList();
        }
        LambdaQueryWrapper<Metric> metricQw = new LambdaQueryWrapper<>();
        metricQw.in(Metric::getGroupRefId, groupIds);
        return this.list(metricQw);
    }

    private TimePeriodDTO getTimePeriodObjByKey(Long metricId, String periodKey) {
        TimePeriodDTO currentPeriod = null;
        if (DEFAULT_METRIC_DATE_PERIOD_KEY.equalsIgnoreCase(periodKey)) {
            currentPeriod = new TimePeriodDTO();
            currentPeriod.setKey(DEFAULT_METRIC_DATE_PERIOD_KEY);
        } else {
            List<TimePeriodDTO> timeperiods = getPeriodsByMetric(metricId, 20);
            if (Strings.isNullOrEmpty(periodKey)) {
                currentPeriod = timeperiods.get(0);
            }
            if (currentPeriod == null) {
                for (TimePeriodDTO timePeriod : timeperiods) {
                    if (timePeriod.getKey().equals(periodKey)) {
                        currentPeriod = timePeriod;
                        break;
                    }
                }
            }
            if (currentPeriod == null) {
                throw new DataFactoryException(ErrorCode.INVALID_TIME_PERIOD);
            }
        }
        return currentPeriod;
    }

}
